package com.song.NacosServiceRegistry;

import com.song.transport.RPCRequest;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;


/**
 * hashKey为String类型，此处采用
 * 将服务器（ip+端口号）进行哈希，映射成环上的一个节点，在请求到来时，根据指定的 hash key 同样映射到环上，并顺时针选取最近的一个服务器节点进行请求（在本图中，使用的是 userId 作为 hash key）。
 * 当环上的服务器较少时，即使哈希算法选择得当，依旧会遇到大量请求落到同一个节点的问题，为避免这样的问题，大多数一致性哈希算法的实现度引入了虚拟节点的概念。
 */
public class ConsistentHashLoaderBalancer implements LoadBalancer{
    private HashStrategy hashStrategy = new JDKHashCodeStrategy();//此处可实现多种 哈希算法使得节点均匀分布

    private final static int VIRTUAL_NODE_SIZE = 5;//每个物理节点默认加入5个虚拟节点
    private final static String VIRTUAL_NODE_SUFFIX = "&&";//虚拟节点的后缀的连接 ip&&number 添加后缀的方式，克隆出了其它几个节点，使得环上的节点分布的均匀。一般来说，物理节点越多，所需的虚拟节点就越少。

    @Override
    public <T> T select(List<T> instances, RPCRequest rpcRequest) {
//        int size = instances.size();
        //将RPCRequest的服务接口名称和服务参数作为hash key，可以保证同一客户端调用的同一个服务在此均衡策略下每次都是在访问同一个服务器
        int uniqueHashCode = hashStrategy.getHashCode(rpcRequest.getInterfaceName() + Arrays.stream(rpcRequest.getParameters()));

        //使用 TreeMap 作为一致性哈希环的数据结构
        TreeMap<Integer, T> ring = buildConsistentHashRing(instances);
        //通过hash key找到映射的服务器
        T instance = locate(ring, uniqueHashCode);

        return instance;
    }

    private <T> T locate(TreeMap<Integer,T> ring, int uniqueHashCode) {
        // 向右找到第一个key,获取环上最近的一个节点
        Map.Entry<Integer, T> locateEntry = ring.ceilingEntry(uniqueHashCode);

        if(locateEntry == null) {
            locateEntry = ring.firstEntry();
        }
        return locateEntry.getValue();
    }

    //构建一致性哈希环的过程，默认加入了 5 个虚拟节点
    private <T> TreeMap<Integer,T> buildConsistentHashRing(List<T> instances) {
        TreeMap<Integer, T> virtualNodeRing = new TreeMap<>();

        for(T instance : instances) {
            for(int i = 0;i < VIRTUAL_NODE_SIZE;i++) {
                //新增虚拟节点的方式如果有影响，也可以抽象出由物理节点扩展虚拟节点的类
                virtualNodeRing.put(hashStrategy.getHashCode(instance.toString() + VIRTUAL_NODE_SUFFIX + i), instance);
            }
        }

        return virtualNodeRing;
    }
}
